<?php

namespace Aitels\Foundation\Generate;

use Aitels\Foundation\Code\ClassAnalyser;
use Illuminate\Support\Str;
use  Aitels\Foundation\Code\ClassBuilder;

class ClassFileGenerator
{
    protected $writer;

    /**
     * 创建类文件简洁语法
     * @param  string|array  $classHints
     * @example $generator->generateByHints(['\App\Controllers\User@create,update', '\App\Controllers\Admin@delete,retrieve'])
     * 注：命名空间会自动映射到文件夹
     */
    public function generateByHints($classHints)
    {
        $files = $this->buildFilesByHints((array) $classHints);
        $this->generate($files);
    }

    /**
     * 创建类文件完整语法
     *     $definitions = [
     *            '\\App\\Controllers\\User' => [
     *                'methods' => [
     *                    'create' => [
     *                        'code'     => '//TODO something',
     *                        'modifier' => ClassBuilder::PUBLIC,
     *                        'comments' => [
     *                            'do something1 ...',
     *                            'do something2 ...'
     *                        ]
     *                    ],
     *                    'update' => [
     *                        'code'     => '//TODO something',
     *                        'modifier' => ClassBuilder::PUBLIC | ClassBuilder::STATIC,
     *                        'comments' => [
     *                            'do something1 ...',
     *                            'do something2 ...'
     *                        ]
     *                    ]
     *                ],
     *                'fields'  => [
     *                    'timestamp' => ClassBuilder::PUBLIC | ClassBuilder::STATIC
     *                ]
     *                'docs'   => [
     *                    '@method int read()',
     *                ]
     *            ]
     *        ];
     * $generator->generateByDefinitions($definitions)
     * @param $definitions
     */
    public function generateByDefinitions($definitions, $overWritten = false)
    {

        $files = $this->buildFilesByDefinitions($definitions);
        $this->generate($files, $overWritten);
    }

    public function generateTraitFilesByDefinitions($definitions, $overWritten = false)
    {
        $files = $this->buildFilesByDefinitions($definitions);
        $this->generateTraitFile($files, $overWritten);
    }

    protected function generateTraitFile($files, $overWritten = false)
    {
        foreach ($files as &$file) {
            $file['content'] = str_replace('class', 'trait', $file['content']);
        }
        $this->generate($files, $overWritten);

    }

    protected function generate($files, $overWritten = false)
    {
        if (!$this->writer) {
            $this->writer = new Writer();
        }
        foreach ($files as $file) {
            $this->writer->write($file['realPath'], $file['content'], $overWritten);
        }
    }


    protected function buildFilesByHints($classHints)
    {
        $files = [];
        foreach ($classHints as $hint) {
            $builder = new ClassBuilder();
            list($namespace, $className, $methods) = $this->parseClassHints($hint);
            $builder->setNamespace($namespace);
            $builder->setClassName($className);
            foreach ($methods as $method) {
                $builder->addMethod($method, "//TODO $method ...", ClassBuilder::PUBLIC);
            }
            $files[] = [
                'realPath' => $this->classToRealPath($namespace.'\\'.$className),
                'content' => $builder->build()
            ];
        }
        return $files;
    }

    protected function buildFilesByDefinitions($definitions)
    {
        $files = [];
        foreach ($definitions as $class => $classDefinition) {
            $builder = new ClassBuilder();
            list($namespace, $className) = $this->parseClassName($class);
            $builder->setNamespace($namespace);
            $builder->setClassName($className);
            if (!empty($classDefinition['extends'])) {
                $builder->setExtends($classDefinition['extends']);
            }


            if (isset($classDefinition['fields'])) {
                foreach ($classDefinition['fields'] as $name => $modifier) {
                    $builder->addField($name, $modifier);
                }
            }

            if (isset($classDefinition['docs'])) {
                foreach ($classDefinition['docs'] as $doc) {
                    $builder->addClassDoc($doc);
                }
            }
            if (isset($classDefinition['uses'])) {
                foreach ($classDefinition['uses'] as $use) {
                    $builder->addUse($use);
                }
            }
            if (isset($classDefinition['useTraits'])) {
                foreach ($classDefinition['useTraits'] as $trait) {
                    $builder->addTrait($trait);
                }
            }


            foreach ($classDefinition['methods'] as $methodName => $methodDefinition) {
                $builder->addMethod($methodName, $methodDefinition['code'], $methodDefinition['modifier'],
                    $methodDefinition['params'] ?? null);
                foreach ($methodDefinition['comments'] as $comment) {
                    $builder->addMethodDoc($methodName, $comment);
                }
            }
            $files[] = [
                'realPath' => $this->classToRealPath($namespace.'\\'.$className),
                'content' => $builder->build()
            ];
        }
        return $files;
    }


    protected function parseClassHints($hint)
    {
        if (Str::contains($hint, '@')) {
            list($className, $methods) = explode('@', $hint);
            $methods = explode(',', $methods);
        } else {
            $className = $hint;
            $methods = [];
        }
        list($namespace, $className) = $this->parseClassName($className);
        return [$namespace, $className, $methods];
    }

    protected function parseClassName($class)
    {

        $class = ltrim($class, '\\');
        $parts = explode('\\', $class);
        $className = end($parts);
        $namespace = substr($class, 0, strrpos($class, "\\$className"));
        return [$namespace, $className];
    }

    protected function classToRealPath($class)
    {
        foreach (ClassAnalyser::getPsr4() as $namespace => $realpath) {
            if (Str::startsWith($class, $namespace)) {
                $class = $realpath.Str::after($class, $namespace);
            }
        }
        return str_replace('\\', DIRECTORY_SEPARATOR, $class).'.php';
    }

}
